#include "YaoAV/YaoReader.h"

extern "C"
{
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include <libavformat/avio.h>

}
#include <stdio.h>


// 自定义数据源结构体 
typedef struct {
    FILE *fp;
    long long file_size;
} LocalFileSource;

// 自定义读取函数 
static int custom_read_function(void *opaque, uint8_t *buf, int buf_size) {
    LocalFileSource *source = (LocalFileSource *)opaque;
    if (!source || !source->fp) {
        return AVERROR(EINVAL);
    }
    size_t bytes_read = fread(buf, 1, buf_size, source->fp);
    if (bytes_read == 0) {
        if (feof(source->fp)) {
            return 0;
        }
//        if (ferror(source->fp)) {
//            fprintf(stderr, "Custom Read: Error reading from file: %s\n", strerror(errno));
//            return AVERROR(EIO);
//        }
    }
    return (int)bytes_read;
}

// 自定义 seek 函数 
static int64_t custom_seek_function(void *opaque, int64_t offset, int whence) {
    LocalFileSource *source = (LocalFileSource *)opaque;
    if (!source || !source->fp) {
        return AVERROR(EINVAL);
    }
    int fseek_whence;
    if (whence == AVSEEK_SIZE) {
        return source->file_size;
    }
    switch (whence) {
        case SEEK_SET: fseek_whence = SEEK_SET; break;
        case SEEK_CUR: fseek_whence = SEEK_CUR; break;
        case SEEK_END: fseek_whence = SEEK_END; break;
        default:
            fprintf(stderr, "Custom Seek: Invalid whence: %d\n", whence);
            return AVERROR(EINVAL);
    }
    if (fseek(source->fp, (long)offset, fseek_whence) != 0) {
//        fprintf(stderr, "Custom Seek: fseek failed (offset: %lld, whence: %d): %s\n",
//                offset, fseek_whence, strerror(errno));
        return AVERROR(EIO);
    }
    long long new_pos = ftell(source->fp);
    return new_pos;
}

int main(int argc, char *argv[]) {
    const char *input_filename = "/Users/yaoyao/Downloads/IMG_9048.mp4";

    LocalFileSource file_source = {0}; // .fp will be NULL, .file_size will be 0
    AVFormatContext *fmt_ctx = NULL;
    AVIOContext *avio_ctx = NULL;
    unsigned char *avio_ctx_buffer = NULL;
    const size_t avio_ctx_buffer_size = 4096;
    int result_code = 0; // Stores the first error encountered

    // Use a do-while(0) loop to allow 'break' for error handling
    // This acts like a single block from which we can break out upon error.
    do {
        // 1. 打开本地文件并获取大小
        file_source.fp = fopen(input_filename, "rb");
        if (!file_source.fp) {
            fprintf(stderr, "Could not open file %s\n", input_filename);
            result_code = 1; // Use a generic error code for non-ffmpeg errors
            break;
        }

        if (fseek(file_source.fp, 0, SEEK_END) != 0) {
            fprintf(stderr, "Could not seek to end of file %s:\n", input_filename);
            result_code = 1;
            break;
        }
        file_source.file_size = ftell(file_source.fp);
        if (file_source.file_size < 0) {
            fprintf(stderr, "Could not get file size for %s\n", input_filename);
            result_code = 1;
            break;
        }
        if (fseek(file_source.fp, 0, SEEK_SET) != 0) {
            fprintf(stderr, "Could not seek to start of file %s\n", input_filename);
            result_code = 1;
            break;
        }
        printf("Opened file '%s', size: %lld bytes.\n", input_filename, file_source.file_size);

        // 2. 分配 AVIOContext 的缓冲区
        avio_ctx_buffer = (unsigned char *)av_malloc(avio_ctx_buffer_size + AV_INPUT_BUFFER_PADDING_SIZE);
        if (!avio_ctx_buffer) {
            fprintf(stderr, "Could not allocate AVIOContext buffer.\n");
            result_code = AVERROR(ENOMEM);
            break;
        }

        // 3. 创建 AVIOContext
        avio_ctx = avio_alloc_context(
                avio_ctx_buffer,
                avio_ctx_buffer_size, // Note: avio_alloc_context needs the actual buffer size, not with padding
                0,
                &file_source,
                custom_read_function,
                NULL,
                custom_seek_function
        );
        if (!avio_ctx) {
            fprintf(stderr, "Could not allocate AVIOContext.\n");
            result_code = AVERROR(ENOMEM);
            break;
        }

        // 4. 创建 AVFormatContext
        fmt_ctx = avformat_alloc_context();
        if (!fmt_ctx) {
            fprintf(stderr, "Could not allocate AVFormatContext.\n");
            result_code = AVERROR(ENOMEM);
            break;
        }
        fmt_ctx->pb = avio_ctx; // 关联 AVIOContext

        // 5. 打开输入流
        int open_ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL);
        if (open_ret < 0) {
            // fmt_ctx is non-NULL here, avformat_close_input will handle cleanup of fmt_ctx and avio_ctx
            result_code = open_ret;
            break;
        }
        // If avformat_open_input succeeds, fmt_ctx "owns" avio_ctx for cleanup.
        // We should not manually free avio_ctx if fmt_ctx is valid and open_input succeeded.

        printf("Successfully opened input '%s' using custom I/O.\n", input_filename);

        // 6. 获取流信息
        int find_ret = avformat_find_stream_info(fmt_ctx, NULL);
        if (find_ret < 0) {
            result_code = find_ret;
            break;
        }

        // 7. 打印媒体信息
        av_dump_format(fmt_ctx, 0, input_filename, 0);

        // 8. （可选）读取一些帧
        AVPacket pkt;
        av_init_packet(&pkt);
        int frame_count = 0;
        printf("Reading a few packets...\n");
        while (av_read_frame(fmt_ctx, &pkt) >= 0) {
            av_packet_unref(&pkt);
            frame_count++;
            if (frame_count >= 5) {
                break;
            }
        }
        printf("Read %d packets.\n", frame_count);

    } while (0); // End of do-while(0) block

    // 9. 清理
    // This cleanup section is always executed, regardless of breaks.

    if (result_code < 0 && result_code != AVERROR_EOF) { // AVERROR_EOF is not an error for reading loop
        char errbuf[AV_ERROR_MAX_STRING_SIZE];
        av_strerror(result_code, errbuf, sizeof(errbuf));
        fprintf(stderr, "Error occurred: %s (%d)\n", errbuf, result_code);
    } else if (result_code > 0) { // Non-ffmpeg error codes (like our '1')
        fprintf(stderr, "Non-ffmpeg error occurred with code: %d\n", result_code);
    }


    if (fmt_ctx) {
        // avformat_close_input will free fmt_ctx.
        // It will also call avio_closep on fmt_ctx->pb (our avio_ctx).
        // avio_closep will free the AVIOContext struct itself.
        // It will NOT free the avio_ctx_buffer if it was provided by the user (which it was).
        avformat_close_input(&fmt_ctx);
    } else if (avio_ctx) {
        // This case applies if fmt_ctx allocation failed or avformat_open_input failed
        // in such a way that fmt_ctx itself is NULL, but avio_ctx was allocated.
        // We need to free the AVIOContext struct.
        avio_context_free(&avio_ctx);
    }

    // The buffer `avio_ctx_buffer` was allocated by us with av_malloc.
    // As per avio_alloc_context documentation, if the buffer is provided by the user,
    // the user is responsible for freeing it.
//    if (avio_ctx_buffer) {
//        av_free(avio_ctx_buffer);
//    }

    if (file_source.fp) {
        fclose(file_source.fp);
    }

    printf("Cleanup complete. Exiting.\n");
    return (result_code != 0 && result_code != AVERROR_EOF) ? 1 : 0;
}
